using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FernUniHagen.CompilerExtension.DataStructures;
using FernUniHagen.CompilerExtension.Functions;
using FernUniHagen.CompilerExtension.Interfaces;
using Roslyn.Compilers.Common;
using Roslyn.Compilers.CSharp;
using Roslyn.Services;

namespace FernUniHagen.CompilerExtension.Adaptor
{
    public class ElementAccessExpressionAdapter : BlockNodeFactory, IMultiplicityConvertible<CommonSyntaxNode>, IReferencable
    {
        #region fields
        ElementAccessExpressionSyntax _elementAccessExpression;
        IDocument _doc;
        BlockSyntax _block;
        StatementSyntax _stmt;
        IdentifierNameSyntax _identifier;
        #endregion
        #region constructors
        public ElementAccessExpressionAdapter(ElementAccessExpressionSyntax node)
        {
            _elementAccessExpression = node;
        }

        public ElementAccessExpressionAdapter(IDocument doc, BlockSyntax block, ElementAccessExpressionSyntax elementAccess, StatementSyntax stmt, IdentifierNameSyntax identifer) : this(elementAccess)
        {
            _doc = doc;
            _block = block;
            _stmt = stmt;
            _identifier = identifer;

        }
        #endregion

        public override StatementReplacement<CommonSyntaxNode> ConvertToMutiplicity(string newVariableName)
        {
            var stmtReplacement = new StatementReplacement<CommonSyntaxNode>(_elementAccessExpression, _elementAccessExpression);

            //First check the leftside of the node for the multiplicity. If it is not Any, we can return
            var leftIdentifier = (IdentifierNameSyntax)_elementAccessExpression.DescendantNodesAndSelf().First(n => n is IdentifierNameSyntax);
            var leftSideNode = SemanticModel.FindDecleration(leftIdentifier);
            var leftDecl = SyntaxElementFactory.GetDeclaration(leftSideNode, SemanticModel);
           
            if (leftDecl.MultiplicityType != AttributeType.Any) return null;

            //Get the argument (the key which we want to delete) and check if it is a list or not
            var argument = (ArgumentSyntax)_elementAccessExpression.DescendantNodesAndSelf().First(n => n is ArgumentSyntax);
            var argumentIdentifier = (SimpleNameSyntax)argument.DescendantNodesAndSelf().First(n => n is SimpleNameSyntax);
            var argumentDefinition = SemanticModel.FindDecleration(argumentIdentifier);
            var argumentDecl = SyntaxElementFactory.GetDeclaration(argumentDefinition, SemanticModel);
            var myType = argumentDecl.GetTypeSyntax();
            var prefixUnarySyntax = _elementAccessExpression.FirstAncestorOrSelf<PrefixUnaryExpressionSyntax>();
            var binaryExpressionSyntax = _elementAccessExpression.FirstAncestorOrSelf<BinaryExpressionSyntax>();

            if(leftDecl.MultiplicityType == AttributeType.Any)
            { 
                //Apply Rules R-QSub 1 & 2
                if (prefixUnarySyntax != null)
                {
                    stmtReplacement = ConvertPrefixUnaryExpression(newVariableName, prefixUnarySyntax, leftIdentifier,myType,argument);
                }
                //Apply Rules R-QAdd 1 & 2
                else if(binaryExpressionSyntax != null && binaryExpressionSyntax.Kind == SyntaxKind.AddAssignExpression)
                {
                    //Now check the right side. Depending on the multiplicity of the right side we will choose a proper replacement
                    ExpressionSyntax expression = (ExpressionSyntax)binaryExpressionSyntax.Right;
                    CommonSyntaxNode declerationNode = null;
                    var multiplicityType = AttributeType.None;
                    if (expression is IdentifierNameSyntax)
                    {
                        declerationNode = SemanticModel.FindDecleration((SimpleNameSyntax)expression);
                        var decl = SyntaxElementFactory.GetDeclaration(declerationNode, SemanticModel);
                        multiplicityType = decl.MultiplicityType;
                    }
                    stmtReplacement = ConvertBinaryExpressionSyntax(newVariableName, binaryExpressionSyntax, multiplicityType, expression, argument);
                }

                else
                {
                    stmtReplacement = ConvertElementAccessExpression(newVariableName, leftIdentifier, argument, leftDecl.GetTypeSyntax());

                }
            }






            return stmtReplacement;
        }

        private StatementReplacement<CommonSyntaxNode> ConvertElementAccessExpression(string newVariableName, IdentifierNameSyntax leftIdentifier, ArgumentSyntax argument, TypeSyntax type)
        {
            var statementReplacements = new StatementReplacement<CommonSyntaxNode>();
            string resultVariable = VariableCreatorService.GetTempVariable();
            var resultStatementInit = Syntax.ParseStatement(string.Format("{0} {1} = null;" + Environment.NewLine,type, resultVariable));
            var conditionStatement = Syntax.ParseStatement(string.Format("if({0} != null) {1} = {2}[{0}];" + Environment.NewLine,
                    argument, resultVariable, newVariableName));
            var expressionReplacement = Syntax.ParseExpression(string.Format("{0}", resultVariable));
            statementReplacements = new StatementReplacement<CommonSyntaxNode>(
                   new List<StatementSyntax>() { resultStatementInit, conditionStatement },
               _elementAccessExpression, expressionReplacement);
            return statementReplacements;
        }
            
        private StatementReplacement<CommonSyntaxNode> ConvertBinaryExpressionSyntax(string newVariableName, BinaryExpressionSyntax binaryExpressionSyntax, AttributeType rightSideMultiplicity,
             ExpressionSyntax rightExpression, ArgumentSyntax argument)
        {
            var statementReplacements = new StatementReplacement<CommonSyntaxNode>();
            string newVariable = VariableCreatorService.GetTempVariable();
            string resultVariable = VariableCreatorService.GetTempVariable();
            var initializeStatement = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, newVariable, rightExpression.ToString()));
            var resultStatementInit = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, resultVariable, newVariableName.ToString()));
            //If we are accessing an "any" field on the right side we want to addrange otherwie we just want to check the element and add it
            if (rightSideMultiplicity == AttributeType.Any)
            {
                
                string indexName = "roslynTempIndex";
                var conditionStatement = Syntax.ParseStatement(string.Format(
                    "if({0} != null && {0}.Count == {1}.Count) for (int {3} = 0; {3} < {0}.Count; {3}++) {2}.Add({0}[{3}], {1}[{3}]);" + Environment.NewLine,
                     argument,
                     newVariable,
                     resultVariable,
                     indexName));
                var expressionReplacement = Syntax.ParseExpression(string.Format("{0} = {1}", newVariableName, resultVariable));
                statementReplacements = new StatementReplacement<CommonSyntaxNode>(
                   new List<StatementSyntax>() { initializeStatement, resultStatementInit, conditionStatement },
               binaryExpressionSyntax, expressionReplacement);



            }
            //Option or none
            else
            {

                //@Text:Because identifier might be a property which has site effects
               
                var conditionStatement = Syntax.ParseStatement(string.Format("if({0} != null  && {1} != null) {2}.Add({0},{1});" + Environment.NewLine,
                    argument, newVariable, resultVariable));
                var expressionReplacement = Syntax.ParseExpression(string.Format("{0} = {1}", newVariableName, resultVariable));
                //NodeToReplaceInBlock.Statement
                // var expressionReplacement = Syntax.ParseExpression(string.Format("{0} != null ? {1}.Add({0}) : {1};"));

                statementReplacements = new StatementReplacement<CommonSyntaxNode>(
                    new List<StatementSyntax>() { initializeStatement, resultStatementInit, conditionStatement },
                binaryExpressionSyntax, expressionReplacement);
                //   StatementReplacements.Add(new StatementReplacement(_binaryExpression, expressionReplacement));

            }
            return statementReplacements;
        }

        private StatementReplacement<CommonSyntaxNode> ConvertPrefixUnaryExpression(string newVariableName, PrefixUnaryExpressionSyntax prefixUnarySyntax, IdentifierNameSyntax leftIdentifier,
            TypeSyntax myType, ArgumentSyntax argument)
        {
            var stmtReplacement = new StatementReplacement<CommonSyntaxNode>();
            string resultVariable = VariableCreatorService.GetTempVariable();
            var resultStatementInit = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, resultVariable, newVariableName));
            if (myType is GenericNameSyntax)
            {
                
                string indexName = "roslynTempIndex";
                
                var conditionStatement = Syntax.ParseStatement(string.Format(
                    "if({0} != null && {0}.Count >0) for(int {1} = 0; {1} < {0}.Count; {1}++) {2}.Remove({0}[{1}]);" + Environment.NewLine,
                    argument, indexName, resultVariable));
                var expressionReplacement = Syntax.ParseExpression(string.Format("{0} = {1}", newVariableName, resultVariable));
                stmtReplacement = new StatementReplacement<CommonSyntaxNode>(
                    new List<StatementSyntax>() { resultStatementInit, conditionStatement },
                    prefixUnarySyntax, expressionReplacement);
            }
            else
            {
                
                
                var conditionStatement = Syntax.ParseStatement(string.Format("if({0} != null) {1}.Remove({0});" + Environment.NewLine,
                    argument, resultVariable));
                var expressionReplacement = Syntax.ParseExpression(string.Format("{0} = {1}", newVariableName, resultVariable));
                stmtReplacement = new StatementReplacement<CommonSyntaxNode>(
                    new List<StatementSyntax>() { resultStatementInit, conditionStatement },
                    prefixUnarySyntax, expressionReplacement);
            }
            return stmtReplacement;
        }

        public SyntaxNodeInfo GetReference()
        {
            SyntaxNodeInfo nodeInfo = new SyntaxNodeInfo(_doc, _block, _elementAccessExpression, _stmt, _identifier);
            return nodeInfo;
        }   
    }
}
